import time
import sha

from twisted.internet import defer
from activist import errors
from activist.Config import Config

##
## the _createcalled member can probably be removed at some point;
## it is to help ensure that create() gets called after __init__
## by user code. MAY WANT TO RETHINK THIS INTERFACE (e.g. enforce it
## in the ctor or something...)
##

DEBUG_CREATE = True

def dateOrder(a,b):
    rtn = cmp(a._time,b._time)
    return -rtn

class Command:
    def __init__(self,obj,name):
        self.__name = name
        self.__obj = obj
    def __call__(self,*args,**kwargs):
        if DEBUG_CREATE and self.__name != 'create' and not self.__obj._createcalled:
            raise "_createcalled is still False in object:" + str(self.__obj) + " for command:" + self.__name
        from activist.CommandProcessor import CommandProcessor
        ver = "command_"+self.__name+"_VERSION"
        if not hasattr(self.__obj,ver):
            raise Weirdness("Command " + self.__name + " lacks a version for " + self.__obj)
        CommandProcessor.serializeCommand(self.__name,getattr(self.__obj,ver),self.__obj,*args,**kwargs)
        return self.__obj._command_execute(self.__name,getattr(self.__obj,ver),*args,**kwargs)

class Data(object):

    def _command_execute(self,commandname,version,*args,**kwargs):
        if Config.debug: print "COMMAND about to be executed",self.id(),commandname,args,kwargs
        if getattr(self,"command_" + commandname + "_VERSION") != version:
            try:
                (args,kwargs) = eval( "self.command_" + commandname + "_upgradeFrom(version,args,kwargs)" )
            except:
                print "ERROR with command:",commandname,version
                for x in args:
                    print "arg",type(x)
                for (k,a) in kwargs:
                    print "kwarg",k,a
                raise

        fun = eval("self.command_%s"%commandname)
        return defer.maybeDeferred(fun,*args,**kwargs)

    def command(self,commandname):
        """
        set up a method called "commandname" to serialize the command;
        basically provides convenience so that your
        "command_something" method can be called as ".something()" and
        get serialized by the command-processor.
        """
        _ = Command(self,commandname)
        exec( "self.%s = _" % commandname )
        
        
    ## things which have to be overriden ##

    def getXML(self):
        """return an XML representation of this object, as a string"""
        raise "subclass responsible " + str(self.__class__)
    def _postRestore(self):
        """
        can override this if you need to do clean-up after all objects
        (including metadata) have been restored.
        """
        return
    def _test(self):
        print "Data._test(): please override me to ensure your object is correct after all post-serialization..."


    def _remove(self):
        """called right before this is removed from the datastore. can override to do fun things."""
        pass
    
    def __eq__(self,other):
        if not other:
            return False
        return other.id() == self.id()
    def __ne__(self,other):
        return not self.__eq__(other)
    
    def __init__(self,desiredid=None):
        self._createcalled = False      # this is essentially debug
        self._metadata = []

        self.command("approve");
        self.command("unapprove");
        self.command("delete");

        if Config.debug: print "DATA: got created for klass",type(self),desiredid
        if desiredid is not None:
            self._hash = desiredid

        from activist.DataStore import DataStore
        DataStore.register(self)

    def comments(self):
        from activist.MetaData import MetaData
        return self.sourcesForRelationship(MetaData.CommentOn)        

    def creator(self):
        from activist.MetaData import MetaData
        meta = self.targetsForRelationship(MetaData.Creator)
        if not len(meta):
            return None
        return meta[0]

    def metadata(self):
        return self._metadata

    def isApproved(self):
        from activist.MetaData import MetaData
        from activist.DataStore import DataStore
        moderated = DataStore.getGenericDataByTitle('moderated')
        for data in self.targetsForRelationship(MetaData.BelongsWith):
            if data == moderated:
                return True
        return False

    def approvedBy(self):
        from activist.MetaData import MetaData
        from activist.DataStore import DataStore
        moderated = DataStore.getGenericDataByTitle('moderated')
        for meta in self.metadataTargetsForRelationship(MetaData.BelongsWith):
            print meta,meta.toData()
            if meta.toData() == moderated:
                return meta.creator()
        return None


    command_delete_VERSION = 0
    def command_delete(self,person):
        #print ind,"command_delete:",self.id(),self
        from activist.DataStore import DataStore
        targets = []
        metalist = self.metadata();
        while len(metalist):
            meta = metalist[0]
            metalist = metalist[1:]
            if meta.fromData() == self:
                targets.append(meta.toData())
            else:
                targets.append(meta.fromData())
            meta.command_delete(person)
                
        DataStore.remove(self)
        
        for target in targets:
            if len(target.metadata()) == 0:
                print "  also removing [no metadata]:",target
            #else:
            #print "  target",target,"survives:"
            #for m in target.metadata():
            #    print "    ",m

    command_approve_VERSION = 0
    def command_approve(self,person):
        if self.isApproved():
            print "ATTEMPT to re-approve",self.id(),type(self),"-- skipping!"
            return
        from activist.MetaData import MetaData,MetaDataFactory
        from activist.DataStore import DataStore
        meta = MetaDataFactory(self,MetaData.BelongsWith,DataStore.getGenericDataByTitle('moderated'))
        MetaDataFactory(meta,MetaData.Creator,person)

    command_unapprove_VERSION = 0
    def command_unapprove(self,person):
        if not self.isApproved():
            print "WARNING: event not approved; attempting to unapprove!"
            
        from activist.MetaData import MetaData
        from activist.DataStore import DataStore
        moderated = DataStore.getGenericDataByTitle('moderated')
        print "trying to unapprove",self
        for meta in self.targetsForRelationship(MetaData.BelongsWith,meta=True):
            if meta.toData() == moderated:
                print "  found data to unapprove",meta
                DataStore.remove(meta)
            else:
                print "  ",meta.toData(),"is not",moderated
        print "UNapproved listing",self,"by",person
    
    def addMetaData(self,meta):
        #print "ADD:",meta,"TO",self
        if self != meta.fromData() and self != meta.toData():
            raise "adding metadata, but neither from nor to is me!"
        if not meta in self._metadata:
            self._metadata.append(meta)
            
    def removeMetaData(self,meta):
        if meta in self._metadata:
            self._metadata.remove(meta)
            #print "dropped meta:",meta,"from",self
        else:
            print "warning: meta not found",meta,self

    __slots__ = ['_metadata', 'approve', 'unapprove', 'delete', '_hash', 'highlight', 'category', 'edit', 'create', '_creationdate']
#     def __setattr__(self,i,value):
#         if not self.__dict__.has_key('_used'):
#             self.__dict__['_used'] = []
            
#         if not i in self.__dict__['_used']:
#             self.__dict__['_used'].append(i)
#         self.__dict__[i] = value

    def __setstate__(self,state):
        self.__dict__ = state
        from activist.DataStore import DataStore
        DataStore.register(self)

    def getMetaXML(self):
        rtn = '  <inlinemetadata>\n'
        for x in self.metadata():
            rtn = rtn + '    <inlinemeta src="%s" type="%s" dst="%s" />\n' % (x.fromData().id(), x._relationship, x.toData().id())
        rtn = rtn + '  </inlinemetadata>\n'
        return rtn

    def _id(self,x):
        """this is for internal use by XMLFactor to set IDs after
        deserialization"""
        from activist.DataStore import DataStore
        DataStore._reregister(self,self._hash,x)
        self._hash = x
        print "ID set:",x
        
    def id(self):
        """
        probably not the best way to do ids...should pass in something
        to hash to __init__

        one could have subclasses call id() when they've added all
        their members, but that's kind of cheesy (and forces subclass
        implementors to remember to do that...)
        
        """
        if not hasattr(self,"_hash"):
            from activist.CommandProcessor import CommandProcessor
            self._hash = CommandProcessor.generateNewId()
        return self._hash


    def targetsForRelationship(self,*args,**kwargs):
        rtn = self.metadataTargetsForRelationship(*args)
        if not 'meta' in kwargs:
            rtn = map( lambda x: x.toData(), rtn )
        return rtn

    def sourcesForRelationship(self,*args,**kwargs):
        rtn = self.metadataInverseTargetsForRelationship(*args)
        if not 'meta' in kwargs:
            rtn = map( lambda x: x.fromData(), rtn )
        return rtn

    ## generally internal-use sorts of things ##

    def __getstate__(self):
        return self.getXML()
    def __setstate__(self,state):
        print "SETSTATE:",state
        raise "not implemented"
        #self.parseXML(state)

    def metadataTargetsForRelationship(self,*args):
        """
        """
        
        rtn = []
#        print "looking for:",args
        for meta in self._metadata:
            for rel in args:
                if meta.relationship() is rel \
                   or isinstance(meta.relationship(),rel):
                    if meta.fromData().id() == self.id():
                        rtn.append(meta)
                        break
#                else:
#                    print "  no good:",meta

        rtn.sort(dateOrder)
        return rtn

    def metadataInverseTargetsForRelationship(self,*args):
        """
        TODO: these should be date-ordered
        """
        
        rtn = []
#        print "unlooking for:",args
        for meta in self._metadata:
            for rel in args:
                if meta.relationship() is rel or isinstance(meta.relationship(),rel):
                    if meta.toData().id() == self.id():
                        rtn.append(meta)
                        break
#                    else:
#                        print "  semi-good:",meta,self.id()
#                else:
#                    print "  no good:",meta
        rtn.sort(dateOrder)
        return rtn
    
